/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package Threads;

import ExternalModel.CreditCardAuthority;
import Model.Checks;
import Model.CreditCard;
import Model.CreditCardLog;
import Model.Customer;
import Model.ExternalDBManager;

import Model.Product;
import Model.ProductPurchase;
import Model.Purchase;
import Model.PurchaseAttempts;
import Model.PurchaseAttemptsPK;
import Model.Store;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.ListIterator;
import java.util.Random;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;

/**
 *
 * @author Bagi + Manolis
 */
public class SalesThread extends Thread {

    private static EntityManager em;
    private static EntityManager exm;
    private int noOfChecks = 0;
    private float totalAmount = 0;
    private int totalPoints = 0;
    private Threads parent;
       

    public SalesThread(EntityManager em, EntityManager exm,String name, Threads parent) {
        super(name);
        this.em = em;
        this.exm = exm;
        if (!em.getTransaction().isActive())
                em.getTransaction().begin();
        if (!exm.getTransaction().isActive())
            exm.getTransaction().begin();
        this.parent = parent;
    }

    public void createPurchaseScenario() {
        synchronized (em){
        //επιλογή τυχαίου καταστήματος
            Store store = getRandomStore();
            Customer customer = getRandomCustomer();
        
            if (!em.getTransaction().isActive())
                em.getTransaction().begin();
            
            System.out.println(" " + this.getName());
            
            Purchase purchase = new Purchase(Calendar.getInstance().getTime(), 0,  getDelivery(customer), "false");
            purchase.setCustomer(customer);
            purchase.setStore(store);
            float totalAmount = 0;
            int totalPoints = 0;
            //ενημερώνει αγορά με το συνολικό κόστος
            List<ProductPurchase> productPurchase = getRandomPurchaseList(store, purchase);
            for(ListIterator<ProductPurchase> iterator = productPurchase.listIterator(); iterator.hasNext();){
                ProductPurchase pp = iterator.next();
                                                                         
                totalAmount = pp.getQuantity() * pp.getProductId().getPrice();
                totalPoints = pp.getQuantity() * pp.getProductId().getPoints();
            }
            purchase.setAmount(totalAmount);
            purchase.setPointsEarned(totalPoints);
            em.persist(purchase);
            //ενημερώνει το κατάστημα και τον πελάτη 
            store.addToPurchaseList(purchase);
            customer.addToPurchaseList(purchase);
        
            //ελέγχει την κάρτα
            CreditCardLog creditCardLog = checkCustomerCard(customer);
            //αν έχει γίνει αποδεκτή η συναλλαγή
        
            if (creditCardLog.getSuccess()){
                updateCustomerObject(customer);
                updateChecks(customer);
                em.persist(customer);
            }
        //ενημέρωσε purchase attempts
        updatePurchase(creditCardLog, purchase);
        }
    }

    //επιλογή τυχαίου καταστήματος
    private Store getRandomStore() {
        if (!em.getTransaction().isActive())
             em.getTransaction().begin();
        List<Store> StoreList = em.createNamedQuery("Store.findAll",Store.class).getResultList();
        Store s = new Store();

        Random random = new Random();
        int index = random.nextInt(StoreList.size());
        s = StoreList.get(index);
        return s;
    }
    
       //επιλογή τυχαίου καταστήματος
    private Customer getRandomCustomer() {
        if (!em.getTransaction().isActive())
             em.getTransaction().begin();
        List<Customer> CustomerList = 
                em.createNamedQuery("Customer.findAll",Customer.class).getResultList();
            Random random = new Random();
            int index = random.nextInt(CustomerList.size());
            Customer customer = CustomerList.get(index);
        return customer;
    } 
    
    

    //επιλογή τυχαίων προϊόντων
    private List<ProductPurchase> getRandomPurchaseList(Store s, Purchase p){
    
       List<ProductPurchase> productPurchaseList = new ArrayList<>();
       //δημιουργία αντιγράφου λίστας προϊόντων καταστήματος
       List<Product> productList = em.createNamedQuery("Product.findAll",Product.class).getResultList();
       Random rand = new Random();    
       int randomNum = 0;
       int quantity = 0;
       int i=0;
       
       //δημιουργία τυχαίου αριθμού από το 1 έως το 20
       if (productList.size()<20){
           randomNum = rand.nextInt(productList.size());
       } else {
           randomNum = rand.nextInt(20) + 1;
       }
    
       do{
            int index = rand.nextInt(productList.size());
            Product selectedProduct = productList.get(index);
            //τυχαία επιλογή ποσότητας
            quantity = rand.nextInt(20) + 1;
            ProductPurchase pp = new ProductPurchase(selectedProduct, p, quantity);
            productPurchaseList.add(pp);
            // Υπολογίζει την τελική τιμή της αγοράς πολλαπλασιάζοντας την ποσότητα επί
            // την τιμή κάθε προϊόντος και προσθέτοντας το σύνολο
            totalAmount += pp.getQuantity() * selectedProduct.getPrice();
            // Παρομοίως υπολογίζει το σύνολο των πόντων της αγοράς
            totalPoints += pp.getQuantity() * selectedProduct.getPoints();
            //αφαιρεί το προϊόν από το αντίγραφο της λίστας των προϊόντων του καταστήματος για
            //να μην επαναεπιλεγεί
            productList.remove(selectedProduct);
            i++;
            } while (i<=randomNum);
       
       return productPurchaseList;
    }

    private boolean getDelivery(Customer c) {
        //εάν η διεύθυνση του πελάτη είναι κενή, παράδοση εμπορευμάτων στο κατάστημα
        if (c.getAddress().isEmpty()) {
            return false;
            //διαφορετικά αποστολή στο σπίτι
        } else {
            return true;
        }
    }
    
    //έλεγχος της κάρτας του πελάτη
    public CreditCardLog checkCustomerCard(Customer customer){
        System.out.println("checking " + customer.getLastName() + " card - " + this.getName());
        //φτιάχνουμε το ημερολόγιο της συναλλαγής
        CreditCardLog creditCardLog = new CreditCardLog();
        
        CreditCardAuthority creditCardAuthority = new CreditCardAuthority();
        
        if(customer.getCreditCardList().isEmpty()){
            //αν ο πελάτης δεν έχει κάρτα απορρίπτεται
            creditCardLog.setSuccess(false);
            //καταγράφεται μια αποτυχημένη προσπάθεια
            creditCardLog.increaseNumberOfAttempts();
            creditCardLog.setAttempt();
            return creditCardLog;
        }else{ 
            //αλλιώς διάβασε την πιστωτική του πελάτη
            CreditCard creditCard = customer.getCreditCardList().iterator().next();
            
            Random random = new Random();
            Date first = new Date();    
            //3 το πολύ προσπάθειες 
            while (creditCardLog.getNumberOfAttempts() < 3 && !creditCardLog.getSuccess()){ 
                 //αύξησε τον αύξοντα αριθμό της προασπάθειας
                creditCardLog.increaseNumberOfAttempts();
                //κατέγραψε την ημερομηνία
                creditCardLog.setAttempt();
                //προσπαθεί να πάρει μια συναλλαγή.
                if (!parent.checkAvailableTransactions()){
                    //αρχικά όρισε ψευδή την επιτυχία
                    creditCardLog.setSuccess(false);
                    
                }else{ 
                    parent.decreaseAvailableTransactions();
                    Date last = new Date();
                    //αν έχουν περάσει 10 sec εγκατάλειψε την προσπάθεια
                    if (first.getTime() - last.getTime() >= 10000) break;
                    //ο entity manager της εξωτερικής βάσης
                    exm = ExternalDBManager.getEm();
                    if(!exm.getTransaction().isActive())
                    exm.getTransaction().begin();
                    try{
                       //ψάξε στην εξωτερική βάση για την κάρτα 
                        creditCardAuthority = (CreditCardAuthority) exm.createNamedQuery("CreditCardAuthority.Match")
                        .setParameter("number", creditCard.getPkNumber())
                        .setParameter("cvv", creditCard.getCvv())
                        .setParameter("ownerName", creditCard.getOwnerName())
                        .getSingleResult();
                        if(creditCardAuthority.getBank().toString().equals(creditCard.getBank().toString())){
                            //κατέγραψε το αποτέλεσμα
                            creditCardLog.setSuccess(true);
                            break;
                        }
                
                    }catch (NoResultException e){
                        //αν δεν βρεθεί η κάρτα 
                        creditCardLog.setSuccess(false);
                        break;
                    }
                }
            } 
       //απελευθερώνει τη συναλλαγή.
        parent.increaseAvailableTransactions(); 
        System.out.println("After checking " + customer.getLastName() + " card - " + this.getName());
        return creditCardLog;
        }
    }

    //ενημέρωση επιταγών και πόντων πελάτη
    public void updateCustomerObject(Customer customer){
        //υπολογισμός πόντων 
        int newPoints = customer.getAvailablePoints() + totalPoints;
        //εάν οι συνολικοί πόντοι του πελάτη είναι > 200
        if (newPoints>=200){
            //υπολόγισε τις νέες επιταγές του πελάτη
            this.noOfChecks = (int) newPoints/200;
            //καθόρισε τους πόντους του
            customer.setAvailablePoints(newPoints-noOfChecks*200);
       }else
            //αλλιώς (πόντοι <200) αποθήκευσε το σύνολο των πόντων
            customer.setAvailablePoints(newPoints);
    }
    
    //ενημέρωση επιταγών
    public void updateChecks(Customer customer){ 
        if (!em.getTransaction().isActive())
            em.getTransaction().begin();
        for(int i=0; i<this.noOfChecks; i++){
            Checks checks = new Checks();
            checks.setCustomer(customer);
            checks.setStatus(true);
            checks.setTimeStamp(new Date());
            customer.addToChecksList(checks);
            em.persist(checks);
            
            }
        em.getTransaction().commit();
      }
            

@Override
    public void run() {
        //κάνει την αγορά
        
        createPurchaseScenario();
       
        
    }

    //ενημερωση πελάτη - purchase
    private void updatePurchase(CreditCardLog creditCardLog,Purchase purchase) {
        if (!em.getTransaction().isActive())
            em.getTransaction().begin();
        
        purchase.setSuccess(String.valueOf(creditCardLog.getSuccess()));
        em.persist(purchase);
        em.getTransaction().commit();
        em.getTransaction().begin();
        List<PurchaseAttempts> purchaseAttemptsList = new ArrayList<>();
        int i=0;
        //για καθε στοιχείο της λίστας Attempt της CreditCardLog
        for(ListIterator<Date> iterator = creditCardLog.getAttemptsList().listIterator(); iterator.hasNext();){
                //φτιάχνει και συμπληρώνει ένα αντικείμενο PurchaseAttempts
                PurchaseAttemptsPK purchaseAttemptsPK = new PurchaseAttemptsPK(i, purchase.getPurchaseId());
                PurchaseAttempts purchaseAttempt = new PurchaseAttempts();
                purchaseAttempt.setPurchaseAttemptsPK(purchaseAttemptsPK);
                purchaseAttempt.setTimeStamp( iterator.next());
                purchaseAttempt.setThreadName(this.getName());
                purchaseAttemptsList.add(purchaseAttempt);
                i++;
            }
            //Αποθηκεύει την αντίστοιχη λίστα
            purchase.setPurchaseAttemptsList(purchaseAttemptsList);
            em.persist(purchase);
           
            //em.persist(purchase);
            em.getTransaction().commit();
        }

   
}
  
